package com.zoe.onelink.core.config;

import cn.hutool.core.codec.Base64;
import com.ulisesbocchio.jasyptspringboot.properties.JasyptEncryptorConfigurationProperties;
import lombok.RequiredArgsConstructor;
import org.jasypt.encryption.StringEncryptor;

import javax.crypto.Cipher;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * <p>标题: Druid配置加密</p>
 * <p>描述: 主要用于适配以前项目已经在使用Druid加密方式</p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-06-21
 */
@RequiredArgsConstructor
public class DruidStringEncryptor implements StringEncryptor {

    private final JasyptEncryptorConfigurationProperties jasyptEncryptorConfigurationProperties;

    @Override
    public String encrypt(String message) {
        try {
            return DruidConfigTools.encrypt(this.jasyptEncryptorConfigurationProperties.getPrivateKeyString(), message);
        } catch (Exception e) {
            throw new RuntimeException("应用配置加密失败");
        }
    }

    @Override
    public String decrypt(String encryptedMessage) {
        try {
            return DruidConfigTools.decrypt(this.jasyptEncryptorConfigurationProperties.getPublicKeyString(), encryptedMessage);
        } catch (Exception e) {
            throw new RuntimeException("应用配置解密失败");
        }
    }


    /**
     * 源码来自Druid工具包
     */
    private static class DruidConfigTools {

        public static String decrypt(String cipherText) throws Exception {
            return decrypt((String) null, cipherText);
        }

        public static String decrypt(String publicKeyText, String cipherText)
                throws Exception {
            PublicKey publicKey = getPublicKey(publicKeyText);

            return decrypt(publicKey, cipherText);
        }

        public static PublicKey getPublicKey(String publicKeyText) {
            if (publicKeyText == null || publicKeyText.length() == 0) {
                throw new IllegalArgumentException("public key not null");
            }

            try {
                byte[] publicKeyBytes = Base64.decode(publicKeyText);
                X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(
                        publicKeyBytes);

                KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SunRsaSign");
                return keyFactory.generatePublic(x509KeySpec);
            } catch (Exception e) {
                throw new IllegalArgumentException("Failed to get public key", e);
            }
        }


        public static String decrypt(PublicKey publicKey, String cipherText)
                throws Exception {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            try {
                cipher.init(Cipher.DECRYPT_MODE, publicKey);
            } catch (InvalidKeyException e) {
                // 因为 IBM JDK 不支持私钥加密, 公钥解密, 所以要反转公私钥
                // 也就是说对于解密, 可以通过公钥的参数伪造一个私钥对象欺骗 IBM JDK
                RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
                RSAPrivateKeySpec spec = new RSAPrivateKeySpec(rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());
                Key fakePrivateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
                cipher = Cipher.getInstance("RSA"); //It is a stateful object. so we need to get new one.
                cipher.init(Cipher.DECRYPT_MODE, fakePrivateKey);
            }

            if (cipherText == null || cipherText.length() == 0) {
                return cipherText;
            }
            byte[] cipherBytes = Base64.decode(cipherText);
            byte[] plainBytes = cipher.doFinal(cipherBytes);

            return new String(plainBytes);
        }


        public static String encrypt(String key, String plainText) throws Exception {
            if (key == null) {
                throw new IllegalArgumentException("key not null");
            }

            byte[] keyBytes = Base64.decode(key);
            return encrypt(keyBytes, plainText);
        }

        public static String encrypt(byte[] keyBytes, String plainText)
                throws Exception {
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory factory = KeyFactory.getInstance("RSA", "SunRsaSign");
            PrivateKey privateKey = factory.generatePrivate(spec);
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            try {
                cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            } catch (InvalidKeyException e) {
                //For IBM JDK, 原因请看解密方法中的说明
                RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
                RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent());
                Key fakePublicKey = KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
                cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.ENCRYPT_MODE, fakePublicKey);
            }

            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
            String encryptedString = Base64.encode(encryptedBytes);

            return encryptedString;
        }

        public static byte[][] genKeyPairBytes(int keySize)
                throws NoSuchAlgorithmException, NoSuchProviderException {
            byte[][] keyPairBytes = new byte[2][];

            KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "SunRsaSign");
            gen.initialize(keySize, new SecureRandom());
            KeyPair pair = gen.generateKeyPair();

            keyPairBytes[0] = pair.getPrivate().getEncoded();
            keyPairBytes[1] = pair.getPublic().getEncoded();

            return keyPairBytes;
        }


    }


}
